{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}

module API.Docs.Generate where

import API.Docs.Commands
import API.Docs.Events
import API.Docs.Responses
import API.Docs.Syntax
import API.Docs.Syntax.Types
import API.Docs.Types
import API.TypeInfo
import Data.Char (isSpace, isUpper, toLower, toUpper)
import qualified Data.List.NonEmpty as L
import Data.Text (Text)
import qualified Data.Text as T

commandsDocFile :: FilePath
commandsDocFile = "./bots/api/COMMANDS.md"

eventsDocFile :: FilePath
eventsDocFile = "./bots/api/EVENTS.md"

typesDocFile :: FilePath
typesDocFile = "./bots/api/TYPES.md"

commandsDocText :: Text
commandsDocText =
  ("# API Commands and Responses\n\n" <> autoGenerated <> "\n")
    <> foldMap commandCatTOC chatCommandsDocs
    <> "\n---\n"
    <> foldMap commandCatText chatCommandsDocs
  where
    commandCatTOC CCCategory {categoryName, commands} =
      (T.pack $ "\n" <> withLink "" categoryName <> "\n")
        <> foldMap commandTOC commands
      where
        commandTOC CCDoc {commandType = ATUnionMember tag _} = T.pack $ "- " <> withLink "" (fstToUpper tag) <> "\n"
    commandCatText CCCategory {categoryName, categoryDescr, commands} =
      (T.pack $ "\n\n## " <> categoryName <> "\n\n" <> categoryDescr <> "\n")
        <> foldMap commandDocText commands
      where
        commandDocText CCDoc {commandType = ATUnionMember tag params, commandDescr, network, syntax, responses, errors} =
          ("\n\n### " <> T.pack (fstToUpper tag) <> "\n\n" <> commandDescr <> "\n\n*Network usage*: " <> networkUsage network <> ".\n")
            <> (if null params then "" else paramsText)
            <> (if syntax == "" then "" else syntaxText (tag, params) syntax)
            <> (if length responses > 1 then "\n**Responses**:\n" else "\n**Response**:\n")
            <> foldMap responseText responses
            <> (if null errors then "" else "\n**Errors**:\n")
            <> foldMap errorText errors
            <> "\n---\n"
          where
            paramsText = "\n**Parameters**:\n" <> fieldsText "./TYPES.md" params
        responseText CRDoc {responseType = ATUnionMember tag fields, responseDescr} =
          (T.pack $ "\n" <> fstToUpper tag <> ": " <> respDescr <> ".\n")
            <> ("- type: \"" <> T.pack tag <> "\"\n")
            <> fieldsText "./TYPES.md" fields
          where
            respDescr = if null responseDescr then camelToSpace tag else responseDescr
        errorText (TD err descr) =
          let descr' = if null descr then camelToSpace err else descr
           in T.pack $ "- " <> fstToUpper err <> ": " <> descr' <> ".\n"

networkUsage :: Maybe UsesNetwork -> Text
networkUsage = \case
  Nothing -> "no"
  Just UNInteractive -> "interactive"
  Just UNBackground -> "background"

syntaxText :: TypeAndFields -> Expr -> Text
syntaxText r syntax =
  "\n**Syntax**:\n"
    <> "\n```\n" <> docSyntaxText r syntax <> "\n```\n"
    <> (if isConst syntax then "" else "\n```javascript\n" <> jsSyntaxText False r syntax <> " // JavaScript\n```\n")
    <> (if isConst syntax then "" else "\n```python\n" <> pySyntaxText r syntax <> " # Python\n```\n")

camelToSpace :: String -> String
camelToSpace [] = []
camelToSpace (x : xs) = toUpper x : go xs
  where
    go [] = []
    go (y : ys)
      | isUpper y = ' ' : toLower y : go ys
      | otherwise = y : go ys

eventsDocText :: Text
eventsDocText =
  ("# API Events\n\n" <> autoGenerated <> "\n")
    <> foldMap eventCatTOC chatEventsDocs
    <> "\n---\n"
    <> foldMap eventCatText chatEventsDocs
  where
    eventCatTOC CECategory {categoryName, mainEvents, otherEvents} =
      (T.pack $ "\n" <> withLink "" categoryName <> "\n")
        <> (if hasOthers then "- Main event" <> plural mainEvents <> "\n" else "")
        <> foldMap eventTOC mainEvents
        <> (if hasOthers then "- Other event" <> plural otherEvents <> "\n" <> foldMap eventTOC otherEvents else "")
      where
        eventTOC CEDoc {eventType = ATUnionMember tag _} = T.pack $ indent <> "- " <> withLink "" (fstToUpper tag) <> "\n"
        hasOthers = not $ null otherEvents
        indent = if hasOthers then "  " else ""
    eventCatText CECategory {categoryName, categoryDescr, mainEvents, otherEvents} =
      (T.pack $ "\n\n## " <> categoryName <> "\n\n" <> categoryDescr <> "\n")
        <> foldMap eventDocText (mainEvents ++ otherEvents)
      where
        eventDocText CEDoc {eventType = ATUnionMember tag fields, eventDescr} =
          (T.pack $ "\n\n### " <> fstToUpper tag <> "\n\n" <> evtDescr)
            <> "\n\n**Record type**:\n"
            <> ("- type: \"" <> T.pack tag <> "\"\n")
            <> fieldsText "./TYPES.md" fields
            <> "\n---\n"
          where
            evtDescr = if null eventDescr then camelToSpace tag <> "." else eventDescr
    plural evts = if length evts == 1 then "" else "s"

typesDocText :: Text
typesDocText =
  ("# API Types\n\n" <> autoGenerated <> "\n")
    <> (foldMap (\t -> T.pack $ "\n- " <> withLink "" (docTypeName t)) chatTypesDocs <> "\n")
    <> foldMap typeDocText chatTypesDocs
  where
    typeDocText CTDoc {typeDef = td@APITypeDef {typeName' = name, typeDef}, typeDescr, typeSyntax} =
      ("\n\n---\n\n## " <> T.pack name <> "\n")
        <> (if T.null typeDescr then "" else "\n" <> typeDescr <> "\n")
        <> typeDefText typeDef
        <> (if typeSyntax == "" then "" else syntaxText (name, self : typeFields) typeSyntax)
      where
        self = APIRecordField "self" (ATDef td)
        typeFields = case typeDef of
          ATDRecord fs -> fs
          ATDUnion ms -> APIRecordField "type" tagType : concatMap (\(ATUnionMember _ fs) -> fs) ms
            where
              tagType = ATDef $ APITypeDef (name <> ".type") $ ATDEnum tags
              tags = L.map (\(ATUnionMember tag _) -> tag) ms
          ATDEnum _ -> []
    typeDefText = \case
      ATDRecord fields -> "\n**Record type**:\n" <> fieldsText "" fields
      ATDEnum cs -> "\n**Enum type**:\n" <> foldMap (\m -> "- \"" <> T.pack m <> "\"\n") cs
      ATDUnion cs -> "\n**Discriminated union type**:\n" <> foldMap constrText cs
        where
          constrText (ATUnionMember tag fields) =
            ("\n" <> T.pack (fstToUpper tag) <> ":\n")
              <> ("- type: \"" <> T.pack tag <> "\"\n")
              <> fieldsText "" fields

fieldsText :: FilePath -> [APIRecordField] -> Text
fieldsText docPath = foldMap $ T.pack . fieldText
  where
    fieldText (APIRecordField name t) = "- " <> name <> ": " <> typeText t <> "\n"
    typeText = \case
      ATPrim (PT t) -> t
      ATDef (APITypeDef t _) -> withLink docPath t
      ATRef t -> withLink docPath t
      ATOptional t -> typeText t <> "?"
      ATArray {elemType} -> "[" <> typeText elemType <> "]"
      ATMap (PT t) valueType -> "{" <> t <> " : " <> typeText valueType <> "}"

autoGenerated :: Text
autoGenerated = "This file is generated automatically."

withLink :: FilePath -> String -> String
withLink docPath s = "[" <> s <> "](" <> docPath <> "#" <> headerAnchor s <> ")"
  where
    headerAnchor = map $ \c -> if isSpace c then '-' else toLower c
